package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IVoucherService;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>
 * 优惠券订购单服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@RequiredArgsConstructor
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    private final ISeckillVoucherService seckillVoucherService;
    private final RedisIdWorker redisIdWorker;
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 优惠券秒杀抢购
     *
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        final SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("活动已结束");
        }
        if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("活动未开始");
        }
        if (seckillVoucher.getStock() < 1) {
            return Result.fail("优惠库存不足");
        }
        final Long userId = UserHolder.getUser().getId(); //获取用户ID
        //创建锁对象（redis分布式锁实现一人一单）
        final SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "Order:" + userId);
        //获取锁
        final boolean tryLock = lock.tryLock(1200L);
        if (!tryLock){ //没有获取到锁
            return Result.fail("一名用户只能购买一次");
        }
        //用户获取到锁
        //获取代理对象(事务)
        try {
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy(); //获取当前代理对象，就是该类的接口
            return proxy.createVoucherOrder(voucherId);
        } catch (IllegalStateException e) {
            throw new RuntimeException(e);
        }finally {
            lock.unlock(); //释放锁这一步是必须的，不释放锁可能造成资源浪费
        }

        /**
         * 注意：在单机系统中（非分布式系统），更新数据用乐观锁，增加数据用悲观锁
         */
    }

    /**
     * 创建优惠券订单
     * @param voucherId 优惠券ID
     * @return
     */
    @Transactional
    public Result createVoucherOrder(Long voucherId){
        final Long userId = UserHolder.getUser().getId();


            Long count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();//根据用户ID和优惠券的ID去优惠券订购表查询，统计数量。

            /**
             * 在秒杀活动抢优惠券的时候，同一个用户多次点击抢券按钮，触发多次请求，于是多次请求会被分成多个独立线程
             * 并行执行。线程A判断count == 0，然后执行优惠券订购逻辑。此时线程B也判断count == 0，这就造成了用户多单的情况。
             */

            //判断是否存在
            if (count > 0) {
                //说明用户已买过
                return Result.fail("用户已经买过一次");
            }
                //扣减库存
                boolean success = seckillVoucherService.update().setSql("stock= stock -1")
                        .eq("voucher_id", voucherId)
                        .gt("stock", 0) //库存值大于0，认为线程是安全的（乐观锁解决方案，适用于修改数据）
                        .update();
                if (!success) {
                    return Result.fail("库存不足");
                }

                //创建优惠券订购单
                final VoucherOrder voucherOrder = new VoucherOrder();
                final long OrderId = redisIdWorker.nextId("Voucher");//生成优惠券订购单ID（全局唯一ID）

                voucherOrder.setVoucherId(voucherId); //设置优惠券ID
                voucherOrder.setId(OrderId); //设置优惠券订购单ID
                voucherOrder.setUserId(userId); //设置用户ID 从ThreadLocal里面取

                save(voucherOrder); //向数据库插入数据

                return Result.ok(OrderId); //向客户端返回优惠券订购单的单号
            }

}
